-- Copyright (c) 2024 Huawei Technologies Co., Ltd.
-- openUBMC is licensed under Mulan PSL v2.
-- You can use this software according to the terms and conditions of the Mulan PSL v2.
-- You may obtain a copy of Mulan PSL v2 at:
--         http://license.coscl.org.cn/MulanPSL2
-- THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
-- EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
-- MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
-- See the Mulan PSL v2 for more details.
local class = require 'mc.class'
local log = require 'mc.logging'
local skynet = require 'skynet'
local defs = require 'domain.defs'
local alarm = require 'alarm.alarm'
local component = require 'domain.device.switch.component'
local _, lsw_drv = pcall(require, 'lsw_drv')
local monitor = require 'domain.debug.monitor'
local phy = class(component)

local mtr = monitor.get_instance()
local PHY_8211_TYPE_BUTT<const> = 0xff
local CONNECT_SLOT_ID<const> = 0
local PHY_PRESENCE<const> = 1
function phy:ctor(obj)
    self.obj = obj
    self.connect_phy = {}
    self.phy_type = PHY_8211_TYPE_BUTT
    self.fix_cnt = 0
    self.record_power_state = 1
end

function phy:hard_reset()
    local ok, err
    for _ = 1, 100 do
        ok, err = pcall(function()
            -- 复位phy：0（下电） 1（上电）
            self.obj.ChipRst = 0
            skynet.sleep(10)
            self.obj.ChipRst = 1
            skynet.sleep(20)
        end)
        if ok then
            log:notice("[lsw]phy hard reset switch success")
            mtr:update_state(defs.NetConfigIdx.PhyHardReset, true)
            return
        end
        skynet.sleep(100)
    end
    mtr:update_state(defs.NetConfigIdx.PhyHardReset, false)
    error(string.format('[lsw]phy hard reset switch fail, obj %s, err %s', self.obj, err))
end

function phy:update_type()
    if self.phy_type ~= PHY_8211_TYPE_BUTT then
        mtr:update_state(defs.NetConfigIdx.UpdateType, true)
        return
    end

    local res = lsw_drv.l_get_phy_type(self.obj.PhysicalAddr)
    mtr:update_state(defs.NetConfigIdx.UpdateType, res)
    if not res then
        error(string.format("get phy addr(%s) type fail", self.obj.PhysicalAddr))
    else
        self.phy_type = res
        log:notice("[lsw] get phy addr(%s) type(%s) success", self.obj.PhysicalAddr, res)
    end
end

local SWCHIPFIX_MAXTIMES<const> = 6
function phy:try_trigger_alarm()
    self.fix_cnt = self.fix_cnt + 1
    if self.fix_cnt >= SWCHIPFIX_MAXTIMES and self.obj.HealthState == 0 then
        log:error("Rtl8367 phy_id=%u fix cnt reach threshold, set alarm(curcnt=%u, threshold=%u)",
            self.obj.PhysicalId, self.fix_cnt, SWCHIPFIX_MAXTIMES)
        self.obj.HealthState = 1
        -- 产生告警
        local alarm_instance = alarm.new()
        alarm_instance:event(defs.ALARM_TYPE.PHY_ERR, self.obj.PhysicalId, self.obj.SlotNumber, true)
    end
end

function phy:try_remove_alarm()
    self.fix_cnt = 0
    if self.obj.HealthState == 1 then
        log:error("Rtl8367 phy_id=%u fix cnt reach threshold, clear alarm(curcnt=%u, threshold=%u)",
            self.obj.PhysicalId, self.fix_cnt, SWCHIPFIX_MAXTIMES)
        self.obj.HealthState = 0
        -- 消除告警
        local alarm_instance = alarm.new()
        alarm_instance:event(defs.ALARM_TYPE.PHY_ERR, self.obj.PhysicalId, self.obj.SlotNumber, false)
    end
end

function phy:handle_powerstate_event()
    if self.record_power_state == self.obj.PowerState then
        return
    end

    log:notice('[lsw] phy chip power state(%s) changed', self.obj.PowerState)
    self.record_power_state = self.obj.PowerState
    local alarm_instance = alarm.new()
    alarm_instance:event(
        defs.ALARM_TYPE.PHY_POWER_OFF, self.obj.PhysicalId, self.obj.SlotNumber, self.record_power_state == 0)
end

function phy:is_power_off()
    return self.obj.PowerState == 0
end

function phy:start_init(is_fix)
    if self:is_power_off() then
        return true
    end
    self:hard_reset()
    skynet.sleep(20)
    self:update_type()
    local res = lsw_drv.l_phy_init(self.phy_type, 0, self.obj.PhysicalAddr, 0)
    mtr:update_state(defs.NetConfigIdx.PhyInit, res)
    if not res then
        error(string.format('[lsw]phy(%d) init fail', self.obj.PhysicalAddr))
    end
    log:notice('[lsw]phy(%d) init success', self.obj.PhysicalAddr)

    if is_fix then
        self:try_trigger_alarm()
    end
end

function phy:is_health()
    self:handle_powerstate_event()
    -- 下电状态不检测
    if self:is_power_off() then
        log:info("[lsw] phy(%d) is power off.", self.obj.PhysicalAddr)
        self:try_remove_alarm()
        return true
    end
    
    local res = lsw_drv.l_rtl_8367_is_phy_abnormal(self.obj.Unit, self.obj.PhysicalId,
        self.obj.PhysicalAddr, self.phy_type, CONNECT_SLOT_ID, PHY_PRESENCE)
    if not res then
        return false
    end

    self:try_remove_alarm()
    return true
end

function phy:get_prop(prop)
    if not self.obj then
        return
    end
    return self.obj[prop]
end

return phy
